package phaseI;



import java.util.HashMap;

public class RecursiveParser {

	HashMap<String, String> characterClasses;
	private char tempToken = ' ';
	private String regex = null;
	private String fillerData = "";
	
	public RecursiveParser(HashMap<String, String> characterClasses) {
		this.characterClasses = characterClasses;
	}
	
	public SyntaxTree parse(String regex) {
		//System.out.println("Using Recursive Parser...");
		
		this.regex = regex;
		
		
		SyntaxTree tree = new SyntaxTree();
		tree.root = rexp();
		//System.out.println(tree.printLevelorder());
		
		//System.out.println("Recursive Parser done!");
		return tree;
	}
	
	private TreeNode rexp() {
		TreeNode node = new TreeNode(fillerData);
		
		TreeNode rexpNode = rexp_1();
		if (attachNode(node, rexpNode)) {
			TreeNode rexpPrimeNode = rexp_prime();
			attachNode(node, rexpPrimeNode);
		}
		return node;
	}
	
	private TreeNode rexp_1() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode rexp2Node = rexp_2();
		if (attachNode(node, rexp2Node)) {
			TreeNode rexpPrimeNode = rexp1_prime();
			attachNode(node, rexpPrimeNode);
		}
		return node;
	}
	
	private TreeNode rexp_prime() {
		TreeNode node = new TreeNode(fillerData);
		if ((tempToken = peekChar()) == Token.UNION) {
			node.addNode(new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			TreeNode rexp1Node = rexp_1();
			if (attachNode(node, rexp1Node)) {
				TreeNode rexpPrimeNode = rexp_prime();
				attachNode(node, rexpPrimeNode);
			}
		}
		else
			return null;
		return node;
	}

	private TreeNode rexp1_prime() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode rexp2 = rexp_2();
		if (attachNode(node, rexp2) && moreToken()) {
			TreeNode rexp1Prime = rexp1_prime();
			attachNode(node, rexp1Prime);
			return node;
		}
		else 
			return null;
	}

	private TreeNode rexp_2() {
		TreeNode node = new TreeNode(fillerData);
		String reChar = null;
		if ((tempToken=peekChar()) == Token.OPEN_PAREN) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			TreeNode rexpNode = rexp();
			if (attachNode(node, rexpNode) && (tempToken=peekChar()) == Token.CLOSE_PAREN) {
				attachNode(node, new TreeNode(""+tempToken));
				matchToken(""+tempToken);
				TreeNode rexp2TailNode = rexp2_tail();
				attachNode(node, rexp2TailNode);
			}
			else
				error("Error matching CLOSE_PAREN; found: "+ peekChar());
		}
		else if (matchRE_CHAR(reChar=getCharOrEscapedChar())) {
			//System.out.println("Matched rechar");
			attachNode(node, new TreeNode(reChar));
			matchToken(reChar);
			TreeNode rexp2TailNode = rexp2_tail();
			attachNode(node, rexp2TailNode);
		}
		else {
			TreeNode rexp3 = rexp_3();
			if (!attachNode(node, rexp3))
				return null;
		}
		
		return node;
	}

	

	private TreeNode rexp2_tail() {
		TreeNode node = new TreeNode(fillerData);
		//System.out.println("Rexp2_tail: Checking *, +, {Empty}");
		if ((tempToken=peekChar()) == Token.STAR || (tempToken=peekChar()) == Token.PLUS) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
		}
		else
			return null;
		
		return node;
	}
	
	private TreeNode rexp_3() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode charClassNode = char_class();
		if (attachNode(node, charClassNode))
			return node;
		else
			return null;
	}

	private TreeNode char_class() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode definedClassNode;
		if ((tempToken=peekChar()) == Token.DOT) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			return node;
		}
		else if ((tempToken=peekChar()) == Token.OPEN_BRACKET) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			TreeNode charClass1 = char_class_1();
			attachNode(node, charClass1);
			return node;
		}
		else if ((definedClassNode=defined_class()) != null) {
			attachNode(node, definedClassNode);
			return node;
		}
		else
			return null;
	}

	private TreeNode char_class_1() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode charSetListNode, excludeSetNode;
		if ((charSetListNode = char_set_list()) != null) {
			attachNode(node, charSetListNode);
		}
		else if ((excludeSetNode = exclude_set()) != null){
			attachNode(node, excludeSetNode);
		}
		else
			return null;
		return node;
	}
	
	private TreeNode char_set_list() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode charSetNode = char_set();
		if (attachNode(node, charSetNode)) {
			TreeNode charSetListNode = char_set_list();
			attachNode(node, charSetListNode);
		}
		else if ((tempToken=peekChar()) == Token.CLOSE_BRACKET) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
		}
		else
			return null;
		return node;
	}
	
	private TreeNode char_set() {
		TreeNode node = new TreeNode(fillerData);
		String clsChar = null;
		if (matchCLS_CHAR(clsChar=getCharOrEscapedChar())) {
			attachNode(node, new TreeNode(clsChar));
			matchToken(clsChar);
			TreeNode charSetTailNode = char_set_tail();
			attachNode(node, charSetTailNode);
		}
		else
			return null;
		return node;
	}

	private TreeNode char_set_tail() {
		TreeNode node = new TreeNode(fillerData);
		if ((tempToken=peekChar()) == Token.DASH) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			tempToken = peekChar();
			String clsChar = null;
			if (matchCLS_CHAR(clsChar=getCharOrEscapedChar())) {
				attachNode(node, new TreeNode(clsChar));
				matchToken(clsChar);
			}
			else
				error("char_set_tail failed!");
		}
		else
			return null;
		return node;
	}

	private TreeNode exclude_set() {
		TreeNode node = new TreeNode(fillerData);
		if ((tempToken=peekChar()) == Token.EXP) {
			
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			TreeNode charSetNode = char_set();
			attachNode(node, charSetNode);
			if ((tempToken=peekChar()) == Token.CLOSE_BRACKET) {
				attachNode(node, new TreeNode(""+tempToken));
				matchToken(""+tempToken);
			}
			
			if (matchIN()) {
				attachNode(node, new TreeNode("IN"));
				matchToken("IN");
			}
			
			TreeNode excludeSetTailNode = exclude_set_tail();
			attachNode(node, excludeSetTailNode);
		}
		else
			return null;
		return node;
	}

	private TreeNode exclude_set_tail() {
		TreeNode node = new TreeNode(fillerData);
		TreeNode charClassNode;
		if ((charClassNode=char_class()) != null) {
			attachNode(node, charClassNode);
		}
		else if ((tempToken=peekChar()) == Token.OPEN_BRACKET) {
			attachNode(node, new TreeNode(""+tempToken));
			matchToken(""+tempToken);
			TreeNode charSetNode = char_set();
			attachNode(node, charSetNode);
			if ((tempToken=peekChar()) == Token.CLOSE_BRACKET) {
				attachNode(node, new TreeNode(""+tempToken));
				matchToken(""+tempToken);
			}
			else
				error("exclude_set_tail failed!");
		}
		else
			return null;
		return node;
	}

	private TreeNode defined_class() {
		StringBuilder charClassLabel = new StringBuilder();
		//System.out.println("Is token? "+(Token.DOLLAR==peekChar()));
		if (peekChar() == Token.DOLLAR) {
			charClassLabel.append(peekChar());
			regex = regex.substring(1);
			for (int i = 0; i < regex.length(); i++) {
				char ch = regex.charAt(i);
				if(!Token.isSpecialChar(ch)) {
					charClassLabel.append(ch);
				}
				else
					break;
			}
			regex = regex.substring(charClassLabel.length()-1);
		}
		
		//Append character class's regex to beginning of tokenBuffer and call rexp()
		String labelValue = characterClasses.get(charClassLabel.toString());
		if (labelValue != null) {
			regex = labelValue + regex;
			return char_class();//rexp();
		}
		else
			return null;
	}
	
	private boolean matchRE_CHAR(String reChar) {
		if (reChar != null && reChar.length() == 1)
			return (Token.matchRE_CHAR(reChar.charAt(0)) == false);
		else if (reChar != null && reChar.length() >= 2) {
			char c = reChar.charAt(0);
			if (c == '\\') {
				c = reChar.charAt(1);
				return Token.matchRE_CHAR(c);
			}
			else
				return (Token.matchRE_CHAR(c) == false);
		}
		else
			return false;
	}
	
	private boolean matchCLS_CHAR(String clsChar) {
		//System.out.println("Match CLS_CHAR called for: "+clsChar);
		if (clsChar != null && clsChar.length() == 1)
			return (Token.matchCLS_CHAR(clsChar.charAt(0)) == false);
		else if (clsChar != null && clsChar.length() >= 2) {
			char c = clsChar.charAt(0);
			if (c == '\\') {
				c = clsChar.charAt(1);
				return Token.matchCLS_CHAR(c);
			}
			else
				return (Token.matchCLS_CHAR(c) == false);
		}
		else
			return false;
	}
	
	private String getCharOrEscapedChar() {
		//System.out.println("GetEscaped called");
		if (moreToken()) {
			if (regex.length() >= 2) {
				String temp = regex.substring(0, 2);
				char c = temp.charAt(0);
				if (c != '\\')
					return ""+c;
				else
					return temp;
			}
			else
				return regex.substring(0, 1);
		}
		else
			return null;
	}
	
	private boolean matchIN() {
		return (regex.length() >= 2 && regex.substring(0, 2).equalsIgnoreCase("IN"));
	}

	private void matchToken(String expectedToken) {
		//token = getToken();
		String temp = regex.substring(0, expectedToken.length());
		if (moreToken() && temp.equals(expectedToken)) {
			regex = regex.substring(temp.length());
			//System.out.println("Matched token: "+ expectedToken);
		}
		else
			error(expectedToken +" did not match current token: "+ temp);
	}
	
	
	//Add new node if not null
	private boolean attachNode(TreeNode currNode, TreeNode newNode) {
		if (newNode == null)
			return false;
		else {
			currNode.addNode(newNode);
			return true;
		}
	}
	
	private char peekChar() {
		if (moreToken()) {
			char tok = regex.charAt(0);
			while (Character.isWhitespace(tok)) {
				regex = regex.substring(1);
				tok = regex.charAt(0);
			}
			return tok;
			/*if (tok == '\\') {
				regex = regex.substring(1);
				tok = regex.charAt(0);
				System.out.println("Token is: "+tok);
				return tok;
			}
			else
				return tok;*/
		}
		else
			return '\0';
	}
	
	//Check if more input to consume
	private boolean moreToken() {
		return (regex.length() > 0);
	}
	
	private void error(String message) {
		System.out.println(message);
		//System.exit(0);
		throw new RuntimeException(message) ;
	}
}
